package de.uni_mannheim.informatik.ai.dame_ai.util;

import java.util.HashMap;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class XmlParser {

	Stack<String> tags;
	Pattern tagPat= Pattern.compile("<(/?)(.+?)>");
	Pattern idPat= Pattern.compile("id='\\d+?'");
	Pattern attribPat= Pattern.compile("(\\S+)\\s*=\\s*'([^<>]*)'");
	Pattern namePat= Pattern.compile("\\S+");
	private String textToParse;
	Matcher tagMatcher;
	private HashMap<Integer, XmlElement> processedElemnts;
	

	/**
	 * Parses xmlInput into a tree of XmlElement Objects.
	 * @param xmlInput Has to be a valid XML Document
	 * @return Root of s tree of XmlElement Objects,
	 * 				that represents the data from xmlInput.
	 */
	public XmlElement parse(String xmlInput) throws InvalidXmlException{
		tagMatcher = tagPat.matcher(xmlInput);
		textToParse=xmlInput;
		getNextTag();		//throw away header
		Tag startTag = getNextTag();
		processedElemnts=new HashMap<Integer, XmlElement>();
		return parseElement(startTag);
	}
	
	
	/**
	 * Parses the XML element specified by startTag. This method invokes itself 
	 * recursively on child elements, creates XMLElement Objects which represent them,
	 * and associates them with their parents.
	 * @param startTag The tag which marks up the start of the element to parse.
	 * @return the root of a tree of XmlElement Objects.
	 * @throws InvalidXmlException 
	 */
	private XmlElement parseElement(Tag startTag) throws InvalidXmlException	{
		int startPosition=tagMatcher.end();
		Tag nextTag=getNextTag();
		if(nextTag==null)
			throw new InvalidXmlException("Invalid XML! Not all opening tags are closed!");
		if(processedElemnts.containsKey(startTag.id))
			return processedElemnts.get(startTag.id);
		if(nextTag.isClosing(startTag)){						
			//element to parse has text content
			String textContent=textToParse.substring(startPosition, tagMatcher.start());
			return new XmlElement(startTag, textContent);
		}
		else{
			//element to parse contains children
			XmlElement element=new XmlElement(startTag);
			processedElemnts.put(startTag.id, element);
			while(!nextTag.isClosing(startTag)){
				XmlElement child=parseElement(nextTag);
				element.addChild(child);
				nextTag=getNextTag();
			}
			return element;
		}
	}

	
	/**
	 * Returns the next Tag found in the XML input
	 * @return next Tag in XML input
	 * @throws InvalidXmlException is thrown if a tag has an id attribute
	 * 					whose value is no int literal.<br/>
	 * 					for example <FooBar id='45z'> 
	 */
	private Tag getNextTag() throws InvalidXmlException{
		if(tagMatcher.find()){
			boolean isClosingTag=tagMatcher.group(1).equals("/");
			String tagText=tagMatcher.group(2);
			Matcher nameMatcher=namePat.matcher(tagText);
			nameMatcher.find();
			String tagName = nameMatcher.group();
			Tag tag=new Tag(tagName, isClosingTag);
			Matcher attribMatcher=attribPat.matcher(tagText);
			while(attribMatcher.find()){
				String attribName=attribMatcher.group(1);
				String attribValue=attribMatcher.group(2);
				if(attribName.equals("id")){
					int id=-1;
					try{
						id=Integer.parseInt(attribValue);
					}	catch(NumberFormatException e){
							throw new InvalidXmlException("Error in XML file: id attribute of tag "+tagName+ " is of invalid format.");
					}
					tag.setId(id);
				}
				else
					tag.addAtribute(attribName, attribValue);
			}
			return tag;
		}
		else 
			return null;
	}
}